/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.dq.core.type.cleanse;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.unidata.mdm.dq.core.type.cleanse.CleanseFunctionConfiguration.CleansePortConfiguration.CleansePortConfigurationBuilder;

/**
 * @author Mikhail Mikhailov
 * Cleanse function configuration top level class.
 */
public class CleanseFunctionConfiguration {
    /**
     * Input ports.
     */
    private final List<CleansePortConfiguration> input;
    /**
     * Input ports.
     */
    private final List<CleansePortConfiguration> output;
    /**
     * Supported execution scopes.
     */
    private final Set<CleanseFunctionExecutionScope> supported;
    /**
     * Constructor.
     */
    public CleanseFunctionConfiguration(CleanseFunctionConfigurationBuilder b) {
        super();
        this.input = Objects.isNull(b.input) ? Collections.emptyList() : b.input;
        this.output = Objects.isNull(b.output) ? Collections.emptyList() : b.output;
        this.supported = Objects.isNull(b.supported) ? Collections.emptySet() : b.supported;
    }
    /**
     * @return the input
     */
    public List<CleansePortConfiguration> getInput() {
        return input;
    }
    /**
     * @return the output
     */
    public List<CleansePortConfiguration> getOutput() {
        return output;
    }
    /**
     * @return the supported
     */
    public Set<CleanseFunctionExecutionScope> getSupported() {
        return supported;
    }
    /**
     * Function configuration builder instance.
     * @return builder instance
     */
    public static CleanseFunctionConfigurationBuilder configuration() {
        return new CleanseFunctionConfigurationBuilder();
    }
    /**
     * Port configuration builder.
     * @return builder instance
     */
    public static CleansePortConfigurationBuilder port() {
        return new CleansePortConfigurationBuilder();
    }
    /**
     * @author Mikhail Mikhailov on Feb 4, 2021
     */
    public static class CleanseFunctionConfigurationBuilder {
        /**
         * Input ports.
         */
        private List<CleansePortConfiguration> input;
        /**
         * Input ports.
         */
        private List<CleansePortConfiguration> output;
        /**
         * Supported execution scopes.
         */
        private Set<CleanseFunctionExecutionScope> supported;
        /**
         * Constructor.
         */
        private CleanseFunctionConfigurationBuilder() {
            super();
        }
        /**
         * @param port the port to set
         */
        public CleanseFunctionConfigurationBuilder supports(CleanseFunctionExecutionScope... scope) {
            if (ArrayUtils.isNotEmpty(scope)) {
                return supports(Arrays.asList(scope));
            }
            return this;
        }
        /**
         * @param scopes the scopes to set
         */
        public CleanseFunctionConfigurationBuilder supports(Collection<CleanseFunctionExecutionScope> scopes) {
            if (CollectionUtils.isNotEmpty(scopes)) {
                if (Objects.isNull(this.supported)) {
                    this.supported = EnumSet.noneOf(CleanseFunctionExecutionScope.class);
                }
                this.supported.addAll(scopes);
            }
            return this;
        }
        /**
         * @param port the port to set
         */
        public CleanseFunctionConfigurationBuilder input(CleansePortConfiguration port) {
            if (Objects.nonNull(port)) {
                if (Objects.isNull(this.input)) {
                    this.input = new ArrayList<>();
                }
                this.input.add(port);
            }
            return this;
        }
        /**
         * @param port the port to set
         */
        public CleanseFunctionConfigurationBuilder output(CleansePortConfiguration port) {
            if (Objects.nonNull(port)) {
                if (Objects.isNull(this.output)) {
                    this.output = new ArrayList<>();
                }
                this.output.add(port);
            }
            return this;
        }
        /**
         * Builds configuration instance.
         * @return configuration instance
         */
        public CleanseFunctionConfiguration build() {
            return new CleanseFunctionConfiguration(this);
        }
    }
    /**
     * @author Mikhail Mikhailov
     * The cleanse function port.
     */
    public static class CleansePortConfiguration {
        /**
         * Name.
         */
        private final String name;
        /**
         * Display name.
         */
        private final Supplier<String> displayName;
        /**
         * Description.
         */
        private final Supplier<String> description;
        /**
         * Value required or not.
         */
        private final boolean required;
        /**
         * Filtering mode for UPathValue ports.
         */
        private final CleanseFunctionPortFilteringMode filteringMode;
        /**
         * Input object types.
         */
        private final Set<CleanseFunctionPortInputType> inputTypes;
        /**
         * Input specific value types.
         */
        private final Set<CleanseFunctionPortValueType> valueTypes;
        /**
         * Constructor.
         */
        private CleansePortConfiguration(CleansePortConfigurationBuilder b) {
            super();

            Objects.requireNonNull(b.name, "Port name must not be null.");
            Objects.requireNonNull(b.inputTypes, "Port input type must not be null.");
            Objects.requireNonNull(b.valueTypes, "Port value type must not be null.");

            this.name = b.name;
            this.displayName = Objects.nonNull(b.displayNameSupplier) ? b.displayNameSupplier : () -> b.displayName;
            this.description = Objects.nonNull(b.descriptionSupplier) ? b.descriptionSupplier : () -> b.description;
            this.required = b.required;
            this.filteringMode = Objects.isNull(b.filteringMode) ? CleanseFunctionPortFilteringMode.MODE_UNDEFINED : b.filteringMode;
            this.inputTypes = b.inputTypes;
            this.valueTypes = b.valueTypes;
        }
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
        /**
         * @return the name
         */
        public String getDisplayName() {
            return displayName.get();
        }
        /**
         * @return the description
         */
        public String getDescription() {
            return description.get();
        }
        /**
         * @return the required
         */
        public boolean isRequired() {
            return required;
        }
        /**
         * @return the filteringMode
         */
        public CleanseFunctionPortFilteringMode getFilteringMode() {
            return filteringMode;
        }
        /**
         * @return the inputTypes
         */
        public Set<CleanseFunctionPortInputType> getInputTypes() {
            return Objects.isNull(inputTypes) ? Collections.emptySet() : inputTypes;
        }
        /**
         * @return the valueTypes
         */
        public Set<CleanseFunctionPortValueType> getValueTypes() {
            return Objects.isNull(valueTypes) ? Collections.emptySet() : valueTypes;
        }
        /**
         * @author Mikhail Mikhailov on Feb 4, 2021
         * Port builder.
         */
        public static class CleansePortConfigurationBuilder {
            /**
             * Name of the port.
             */
            private String name;
            /**
             * Plain display name.
             */
            private String displayName;
            /**
             * Display name supplier to support i18n.
             */
            private Supplier<String> displayNameSupplier;
            /**
             * Description in plain text.
             */
            private String description;
            /**
             * Description as supplier.
             */
            private Supplier<String> descriptionSupplier;
            /**
             * Value required or not.
             */
            private boolean required;
            /**
             * Filtering mode for UPathValue ports.
             */
            private CleanseFunctionPortFilteringMode filteringMode;
            /**
             * Input object type.
             */
            private Set<CleanseFunctionPortInputType> inputTypes;
            /**
             * Input specific value type.
             */
            private Set<CleanseFunctionPortValueType> valueTypes;
            /**
             * Constructor.
             */
            private CleansePortConfigurationBuilder() {
                super();
            }
            /**
             * @param name the name to set
             */
            public CleansePortConfigurationBuilder name(String name) {
                this.name = name;
                return this;
            }
            /**
             * @param displayName the displayName to set
             */
            public CleansePortConfigurationBuilder displayName(String displayName) {
                this.displayName = displayName;
                return this;
            }
            /**
             * @param displayNameSupplier the displayNameSupplier to set
             */
            public CleansePortConfigurationBuilder displayName(Supplier<String> displayNameSupplier) {
                this.displayNameSupplier = displayNameSupplier;
                return this;
            }
            /**
             * @param description the description to set
             */
            public CleansePortConfigurationBuilder description(String description) {
                this.description = description;
                return this;
            }
            /**
             * @param descriptionSupplier the descriptionSupplier to set
             */
            public CleansePortConfigurationBuilder description(Supplier<String> descriptionSupplier) {
                this.descriptionSupplier = descriptionSupplier;
                return this;
            }
            /**
             * @param required the required to set
             */
            public CleansePortConfigurationBuilder required(boolean required) {
                this.required = required;
                return this;
            }
            /**
             * @param filteringMode the filteringMode to set
             */
            public CleansePortConfigurationBuilder filteringMode(CleanseFunctionPortFilteringMode filteringMode) {
                this.filteringMode = filteringMode;
                return this;
            }
            /**
             * @param inputTypes the inputTypes to set
             */
            public CleansePortConfigurationBuilder inputTypes(CleanseFunctionPortInputType... inputTypes) {
                if (ArrayUtils.isNotEmpty(inputTypes)) {
                    return inputTypes(Arrays.asList(inputTypes));
                }
                return this;
            }
            /**
             * @param inputTypes the inputTypes to set
             */
            public CleansePortConfigurationBuilder inputTypes(Collection<CleanseFunctionPortInputType> inputTypes) {
                if (CollectionUtils.isNotEmpty(inputTypes)) {
                    if (Objects.isNull(this.inputTypes)) {
                        this.inputTypes = EnumSet.noneOf(CleanseFunctionPortInputType.class);
                    }
                    this.inputTypes.addAll(inputTypes);
                }
                return this;
            }
            /**
             * @param valueTypes the valueTypes to set
             */
            public CleansePortConfigurationBuilder valueTypes(CleanseFunctionPortValueType... valueTypes) {
                if (ArrayUtils.isNotEmpty(valueTypes)) {
                    return valueTypes(Arrays.asList(valueTypes));
                }
                return this;
            }
            /**
             * @param valueTypes the valueTypes to set
             */
            public CleansePortConfigurationBuilder valueTypes(Collection<CleanseFunctionPortValueType> valueTypes) {
                if (CollectionUtils.isNotEmpty(valueTypes)) {
                    if (Objects.isNull(this.valueTypes)) {
                        this.valueTypes = EnumSet.noneOf(CleanseFunctionPortValueType.class);
                    }
                    this.valueTypes.addAll(valueTypes);
                }
                return this;
            }
            /**
             * Builds port.
             * @return port
             */
            public CleansePortConfiguration build() {
                return new CleansePortConfiguration(this);
            }
        }
    }
}
